package com.partsoft.dits.dsm;

import java.sql.Connection;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextStoppedEvent;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

import com.partsoft.dits.AbstractConfigurableDescriptorManager;
import com.partsoft.dits.ConfigurableBean;
import com.partsoft.dits.DescriptorAccepter;
import com.partsoft.dits.log.Log;
import com.partsoft.utils.ClassUtils;

/**
 * 上下文关联数据源管理器
 */
public class DefaultContextDataSourceManager extends BaseDatasourceFactory implements DataSourceManager,
		ConfigurableBean, ApplicationContextAware, ApplicationListener<ApplicationEvent> {

	private boolean validateDescriptor = true;

	private DsmSQLProviderFactory dsmSQLProviderFactory = DefaultDsmSQLProviderFactory.get();

	private DataSourceConfiguredDelegate configuredDelegate = new DataSourceConfiguredDelegate();

	public DefaultContextDataSourceManager() {
	}

	public DefaultContextDataSourceManager(String contextConfigLocation) {
		setContextProfileLocation(contextConfigLocation);
	}

	public DefaultContextDataSourceManager(String contextConfigLocation, boolean createNoExists) {
		setContextProfileLocation(contextConfigLocation);
		setCreateProfileNoExists(createNoExists);
	}

	public void setDsmSQLProviderFactory(DsmSQLProviderFactory dsmSQLProviderFactory) {
		this.dsmSQLProviderFactory = dsmSQLProviderFactory;
	}

	public void setApplicationContext(ApplicationContext ownerContext) throws BeansException {
		configuredDelegate.setApplicationContext(ownerContext);
	}

	public void onApplicationEvent(ApplicationEvent event) {
		if (getOwnerContext() == null)
			return;
		if (!ObjectUtils.nullSafeEquals(getOwnerContext(), event.getSource()))
			return;
		if (event instanceof ContextRefreshedEvent) {
			initContext();
		} else if (event instanceof ContextStoppedEvent) {
			finiContext();
		}
	}

	public boolean hasDescriptor(DataSourceDescriptor descriptor) {
		return configuredDelegate.hasDescriptor(descriptor);
	}

	public DataSourceDescriptor removeDescriptor(DataSourceDescriptor descriptor) {
		return configuredDelegate.removeDescriptor(descriptor);
	}

	public Class<DataSourceDescriptor> getSupportClass() {
		return DataSourceDescriptor.class;
	}

	public int countDescriptor() {
		return configuredDelegate.countDescriptor();
	}

	public boolean isValidateDescriptor() {
		return validateDescriptor;
	}

	public void setValidateDescriptor(boolean validateDescriptor) {
		this.validateDescriptor = validateDescriptor;
	}

	public int countDescriptor(DescriptorAccepter<DataSourceDescriptor> accepter) {
		if (accepter == null)
			return countDescriptor();
		DataSourceDescriptor[] descs = getDescriptors();
		int result = 0;
		for (DataSourceDescriptor desc : descs) {
			if (accepter.accept(desc))
				result++;
		}
		return result;
	}

	public void finiContext() {
		configuredDelegate.finiContext();
	}

	public void initContext() {
		configuredDelegate.initContext();
	}

	public DataSourceDescriptor[] getDescriptors() {
		return configuredDelegate.getDescriptors();
	}

	public DataSourceDescriptor[] getDescriptors(DescriptorAccepter<DataSourceDescriptor> accepter) {
		DataSourceDescriptor[] descs = getDescriptors();
		if (accepter == null)
			return descs;
		List<DataSourceDescriptor> returnList = new LinkedList<DataSourceDescriptor>();
		for (DataSourceDescriptor desc : descs) {
			if (accepter.accept(desc))
				returnList.add(desc);
		}
		return returnList.toArray(new DataSourceDescriptor[0]);
	}

	public DataSourceDescriptor addDescriptor(DataSourceDescriptor dataSourceDescriptor) {
		return configuredDelegate.addDescriptor(dataSourceDescriptor);
	}

	public DataSourceDescriptor modifyDescriptor(DataSourceDescriptor dsinfo) {
		return configuredDelegate.modifyDescriptor(dsinfo);
	}

	private void doAddDataSource(String name, DataSource ds) {
		super.addDataSource(name, ds);
	}

	// 委派实现开始
	public void setOwnerContext(ApplicationContext ownerContext) {
		configuredDelegate.setOwnerContext(ownerContext);
	}

	public ApplicationContext getOwnerContext() {
		return configuredDelegate.getOwnerContext();
	}

	public void setProfileCanBeWrite(boolean profileCanBeWrite) {
		configuredDelegate.setProfileCanBeWrite(profileCanBeWrite);
	}

	public void setCreateProfileNoExists(boolean createNoExists) {
		configuredDelegate.setCreateProfileNoExists(createNoExists);
	}

	public String getContextProfileLocation() {
		return configuredDelegate.getContextProfileLocation();
	}

	public void setContextProfileLocation(String contextConfigLocation) {
		configuredDelegate.setContextProfileLocation(contextConfigLocation);
	}

	public boolean isProfileCanBeWrite() {
		return configuredDelegate.isProfileCanBeWrite();
	}

	public boolean isCreateProfileNoExists() {
		return configuredDelegate.isCreateProfileNoExists();
	}

	public boolean isAlreadyInit() {
		return configuredDelegate.isAlreadyInit();
	}

	// 委派实现结束
	public DataSourceDescriptor getDescriptor(String id) {
		return configuredDelegate.getDescriptor(id);
	}

	public DataSourceDescriptor removeDescriptor(String id) {
		return configuredDelegate.removeDescriptor(id);
	}

	@Override
	public void addDataSource(String name, DataSource ods) {
		Assert.notNull(ods);
		Assert.isInstanceOf(BasicDataSource.class, ods,
				String.format("is not configurable data source object(%s)", ods.toString()));
		BasicDataSource ds = (BasicDataSource) ods;
		DataSourceDescriptor dsinfo = new DataSourceDescriptor(name);
		dsinfo.setDriverClassName(ds.getDriverClassName());
		dsinfo.setInitialSize(ds.getInitialSize());
		dsinfo.setMaxActive(ds.getMaxActive());
		dsinfo.setMaxWait((int) ds.getMaxWait());
		dsinfo.setUsername(ds.getUsername());
		dsinfo.setPassword(ds.getPassword());
		dsinfo.setUrl(ds.getUrl());
		dsinfo.setConfigurable(true);
		dsinfo.setTitle(name);
		dsinfo.setValidationQuery(ds.getValidationQuery());
		configuredDelegate.addDescriptor(dsinfo);
	}

	public DataSourceDescriptor getDescriptor(DataSourceDescriptor descriptor) {
		return getDescriptor(descriptor.getName());
	}

	public boolean hasDescriptor(String dsName) {
		return configuredDelegate.hasDescriptor(dsName);
	}

	@Override
	public DataSource removeDataSource(String dsName) {
		synchronized (this) {
			DataSource ds = super.removeDataSource(dsName);
			configuredDelegate.removeDescriptor(dsName);
			return ds;
		}
	}

	private void doRemoveDataSource(String dsName) {
		super.removeDataSource(dsName);
	}

	private class DataSourceConfiguredDelegate extends AbstractConfigurableDescriptorManager<DataSourceDescriptor> {

		public Class<DataSourceDescriptor> getSupportClass() {
			return DataSourceDescriptor.class;
		}

		@Override
		protected void beforeRemoveDescriptor(DataSourceDescriptor descriptor) {
		}

		@Override
		protected void postRemoveDescriptor(DataSourceDescriptor descriptor) {
			doRemoveDataSource(descriptor.getName());
		}

		@Override
		protected void beforeLoadConfiguredDescriptor(DataSourceDescriptor descriptor) {
		}

		protected DefaultContextDataSourceManager getExternalSuper() {
			return DefaultContextDataSourceManager.this;
		}

		@Override
		protected void setupDescriptorIdentifier(DataSourceDescriptor descriptor, String id) {
			descriptor.setName(id);
		}

		@Override
		protected Properties beforeAddDescriptor(DataSourceDescriptor dsinfo) {
			try {
				ClassUtils.forName(dsinfo.getDriverClassName());
			} catch (Throwable e) {
				throw new IllegalArgumentException("driver error", e);
			}
			String dsName = dsinfo.getName();
			BasicDataSource ds = new BasicDataSource();
			ds.setDriverClassName(dsinfo.getDriverClassName());
			ds.setUrl(dsinfo.getUrl());
			ds.setUsername(dsinfo.getUsername());
			ds.setPassword(dsinfo.getPassword());
			ds.setInitialSize(dsinfo.getInitialSize());
			ds.setMaxActive(dsinfo.getMaxActive());
			ds.setMaxWait(dsinfo.getMaxWait());
			ds.setDefaultAutoCommit(false);
			String ValideSQL = "";

			if (isValidateDescriptor()) {
				try {
					Connection conn = DataSourceUtils.getConnection(ds);
					ValideSQL = dsmSQLProviderFactory.getDsmSQLProvider(conn).getConnectValidateSQL();
					DataSourceUtils.releaseConnection(conn, ds);
				} catch (Throwable e) {
					String errorInfo = String.format("Occur an error when config \"%s\"", dsinfo.toString());
					Log.error(errorInfo);
					throw new IllegalArgumentException(errorInfo, e);
				}
			}

			Properties confProperties = new Properties();
			confProperties.put("driverClassName", dsinfo.getDriverClassName());
			confProperties.put("url", dsinfo.getUrl());
			confProperties.put("username", dsinfo.getUsername());
			confProperties.put("password", dsinfo.getPassword());
			confProperties.put("initialSize", String.valueOf(dsinfo.getInitialSize()));
			confProperties.put("maxActive", String.valueOf(dsinfo.getMaxActive()));
			confProperties.put("maxWait", String.valueOf(dsinfo.getMaxWait()));
			confProperties.put("defaultAutoCommit", "false");
			confProperties.put("testOnBorrow", "true");
			confProperties.put("validationQuery", ValideSQL);

			dsinfo.setValidationQuery(ValideSQL);
			ds.setTestOnBorrow(true);
			ds.setValidationQuery(ValideSQL);
			getExternalSuper().doAddDataSource(dsName, ds);
			return confProperties;
		}

		@Override
		protected void validateDescriptor(DataSourceDescriptor descriptor) {
			Assert.hasText(descriptor.getName(), "Must be set datasource name");
		}

		@Override
		protected String resolveDescriptorIdentifier(DataSourceDescriptor descriptor) {
			return descriptor.getName();
		}

		@Override
		protected Class<?> resolveConfiguredBeanClass() {
			return BasicDataSource.class;
		}

		@Override
		protected DataSourceDescriptor convertConfiguredBean(String beanId, Object rawObject) {
			Assert.isInstanceOf(BasicDataSource.class, rawObject,
					String.format("不是一个可配置的数据源对象(%s)", rawObject.toString()));
			BasicDataSource ds = (BasicDataSource) rawObject;
			DataSourceDescriptor dsinfo = new DataSourceDescriptor(beanId);
			dsinfo.setDriverClassName(ds.getDriverClassName());
			dsinfo.setInitialSize(ds.getInitialSize());
			dsinfo.setMaxActive(ds.getMaxActive());
			dsinfo.setMaxWait((int) ds.getMaxWait());
			dsinfo.setUsername(ds.getUsername());
			dsinfo.setPassword(ds.getPassword());
			dsinfo.setUrl(ds.getUrl());
			dsinfo.setConfigurable(true);
			dsinfo.setValidationQuery(ds.getValidationQuery());
			doAddDataSource(beanId, ds);
			return dsinfo;
		}

		@Override
		protected void beforeUnLoadConfiguredDescriptor(DataSourceDescriptor descriptor) {
			doRemoveDataSource(descriptor.getName());
		}
	}

}
